package com.clei.utils;

import com.clei.dto.file.FileName;

import javax.imageio.ImageIO;
import java.awt.Color;
import java.awt.DisplayMode;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/**
 * 图片处理工具类
 *
 * @author KIyA
 */
public final class ImageUtil {

    /**
     * 点符号
     */
    private static final String DOT = ".";

    /**
     * 图片默认格式
     */
    private static final String FORMAT = "png";

    /**
     * 图片默认输出路径
     */
    private static final String OUT_PATH = "E:\\tmp";

    /**
     * 背景色
     */
    private static final Color BG_COLOR = Color.BLACK;

    /**
     * 画点色
     */
    private static final Color DOT_COLOR = Color.WHITE;

    /**
     * 字符串长度arr
     */
    private static final int[] STR_LENGTH_ARR;

    /**
     * 单边基础方块个数
     */
    private static final int[] SIDE_SQUARE_COUNT_ARR;

    /**
     * HEX_DIGITS
     */
    private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    /**
     * hex map
     */
    private static final Map<Character, Integer> HEX_MAP = new HashMap<>(HEX_DIGITS.length);

    static {
        int length = 512;
        STR_LENGTH_ARR = new int[length];
        SIDE_SQUARE_COUNT_ARR = new int[length];
        for (int i = 0; i < STR_LENGTH_ARR.length; i++) {
            STR_LENGTH_ARR[i] = (i + 1) * (i + 1);
            SIDE_SQUARE_COUNT_ARR[i] = (i + 1) * 8;
        }
        for (int i = 0; i < HEX_DIGITS.length; i++) {
            HEX_MAP.put(HEX_DIGITS[i], i);
        }
    }

    /**
     * 裁剪图片
     *
     * @param imageFilePath  源图片文件路径
     * @param targetFilePath 裁剪图片存放路径，需包含文件名
     * @param x              x起点
     * @param y              y起点
     * @param w              宽度
     * @param h              高度
     * @throws Exception 各种异常
     */
    public static void cut(String imageFilePath, String targetFilePath, int x, int y, int w, int h) throws Exception {
        File imageFile = new File(imageFilePath);
        BufferedImage image = ImageIO.read(imageFile);
        int width = image.getWidth() - x;
        int height = image.getHeight() - y;
        w = Math.min(w, width);
        h = Math.min(h, height);
        BufferedImage resultImage = image.getSubimage(x, y, w, h);
        // 输出
        String suffix = FileUtil.getSuffix(imageFilePath);
        ImageIO.write(resultImage, suffix, new File(targetFilePath));
    }

    /**
     * 分割图片
     *
     * @param imageFilePath  源图片文件路径
     * @param targetFilePath 裁剪图片存放路径，需包含文件名
     * @param w              宽度
     * @param h              高度
     * @throws Exception 各种异常
     */
    public static void split(String imageFilePath, String targetFilePath, int w, int h) throws Exception {
        File imageFile = new File(imageFilePath);
        BufferedImage image = ImageIO.read(imageFile);
        int width = image.getWidth();
        int height = image.getHeight();
        if (w > width / 2 || h > height / 2) {
            throw new RuntimeException("分割尺寸不能超过原图的二分之一");
        }
        // 输出图片
        String suffix = FileUtil.getSuffix(imageFilePath);
        File f1 = new File(targetFilePath + File.separator + "1." + suffix);
        File f2 = new File(targetFilePath + File.separator + "2." + suffix);
        BufferedImage out1 = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        BufferedImage out2 = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g1 = out1.createGraphics();
        Graphics2D g2 = out2.createGraphics();
        // 横竖方块数
        int xCount = width / w;
        if (0 != width % w) {
            xCount++;
        }
        int yCount = height / h;
        if (0 != height % h) {
            yCount++;
        }

        boolean flag = true;
        for (int i = 0; i < yCount; i++) {
            for (int j = 0; j < xCount; j++) {
                int x = j * w;
                int y = i * h;
                int ww = (j == xCount - 1) ? width - x : w;
                int hh = (i == yCount - 1) ? height - y : h;
                BufferedImage tempImg = image.getSubimage(x, y, ww, hh);
                if (flag) {
                    if (NumUtil.isOdd(j)) {
                        g2.drawImage(tempImg, x, y, null);
                    } else {
                        g1.drawImage(tempImg, x, y, null);
                    }
                } else {
                    if (NumUtil.isOdd(j)) {
                        g1.drawImage(tempImg, x, y, null);
                    } else {
                        g2.drawImage(tempImg, x, y, null);
                    }
                }
            }
            flag = !flag;
        }
        g1.dispose();
        g2.dispose();

        ImageIO.write(out1, suffix, f1);
        ImageIO.write(out2, suffix, f2);
    }

    /**
     * 手撕照片
     *
     * @param imageFilePath  源图片文件路径
     * @param targetFilePath 裁剪图片存放路径，需包含文件名
     * @param w              宽度
     * @throws Exception 各种异常
     */
    public static void splitHorizontal(String imageFilePath, String targetFilePath, int w) throws Exception {
        splitHorizontalWithBg(imageFilePath, targetFilePath, null, w);
    }

    /**
     * 手撕照片
     *
     * @param imageFilePath  源图片文件路径
     * @param targetFilePath 裁剪图片存放路径，需包含文件名
     * @param h              高度
     * @throws Exception 各种异常
     */
    public static void splitLongitudinal(String imageFilePath, String targetFilePath, int h) throws Exception {
        splitLongitudinalWithBg(imageFilePath, targetFilePath, null, h);
    }

    /**
     * 手撕照片
     *
     * @param imageFilePath  源图片文件路径
     * @param targetFilePath 裁剪图片存放路径，需包含文件名
     * @param bgFilePath     背景图片文件路径
     * @param w              宽度
     * @throws Exception 各种异常
     */
    public static void splitHorizontalWithBg(String imageFilePath, String targetFilePath, String bgFilePath, int w) throws Exception {
        File imageFile = new File(imageFilePath);
        BufferedImage image = ImageIO.read(imageFile);
        boolean hasBg = StringUtil.isNotBlank(bgFilePath);
        BufferedImage bgImage = hasBg ? ImageIO.read(new File(bgFilePath)) : null;
        int width = image.getWidth();
        int height = image.getHeight();
        if (w > width / 2) {
            throw new RuntimeException("分割尺寸不能超过原图的二分之一");
        }
        // 输出图片
        FileName fileName = FileUtil.getFileName(imageFilePath);
        BufferedImage targetImage = new BufferedImage(width + w, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics = targetImage.createGraphics();

        Random rand = new Random();
        int baseX = width / 2;
        int curX = baseX;
        boolean turnLeft = true;
        for (int i = 0; i < height; i++) {
            // 使得不同裂痕的长度不一
            if (rand.nextBoolean()) {
                turnLeft = !turnLeft;
            }

            int length = rand.nextInt(10);
            if (turnLeft) {
                if (baseX - curX - length > w) {
                    curX = curX + length;
                    turnLeft = false;
                } else {
                    curX = curX - length;
                }
            } else {
                if (curX + length - baseX > w) {
                    curX = curX - length;
                    turnLeft = true;
                } else {
                    curX = curX + length;
                }
            }

            BufferedImage left = image.getSubimage(0, i, curX, 1);
            BufferedImage right = image.getSubimage(curX, i, width - curX, 1);
            if (hasBg) {
                BufferedImage bg = bgImage.getSubimage(0, i, w, 1);
                graphics.drawImage(bg, curX, i, null);
            }
            graphics.drawImage(left, 0, i, null);
            graphics.drawImage(right, w + curX, i, null);

        }
        graphics.dispose();
        ImageIO.write(targetImage, fileName.getSuffix(), new File(targetFilePath));
    }

    /**
     * 手撕照片
     * 长度不增长
     *
     * @param imageFilePath  源图片文件路径
     * @param targetFilePath 裁剪图片存放路径，需包含文件名
     * @param bgFilePath     背景图片文件路径
     * @param baseX          撕裂位置
     * @param w              宽度
     * @throws Exception 各种异常
     */
    public static void splitHorizontalWithBg(String imageFilePath, String targetFilePath, String bgFilePath, int baseX, int w) throws Exception {
        File imageFile = new File(imageFilePath);
        BufferedImage image = ImageIO.read(imageFile);
        boolean hasBg = StringUtil.isNotBlank(bgFilePath);
        BufferedImage bgImage = hasBg ? ImageIO.read(new File(bgFilePath)) : null;
        int width = image.getWidth();
        int height = image.getHeight();
        if (w >= Math.min(baseX, width - baseX)) {
            throw new RuntimeException("分割尺寸不能超过分割位置");
        }
        // 输出图片
        FileName fileName = FileUtil.getFileName(imageFilePath);
        BufferedImage targetImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics = targetImage.createGraphics();

        Random rand = new Random();
        int curX = baseX;
        boolean turnLeft = true;
        for (int i = 0; i < height; i++) {
            // 使得不同裂痕的长度不一
            if (rand.nextBoolean()) {
                turnLeft = !turnLeft;
            }

            int length = rand.nextInt(10);
            if (turnLeft) {
                if (baseX - curX - length > w) {
                    curX = curX + length;
                    turnLeft = false;
                } else {
                    curX = curX - length;
                }
            } else {
                if (curX + length - baseX > w) {
                    curX = curX - length;
                    turnLeft = true;
                } else {
                    curX = curX + length;
                }
            }

            int newX = curX - w / 2;
            BufferedImage left = image.getSubimage(0, i, newX, 1);
            BufferedImage right = image.getSubimage(newX + w, i, width - newX - w, 1);
            if (hasBg) {
                BufferedImage bg = bgImage.getSubimage(0, i, w, 1);
                graphics.drawImage(bg, newX, i, null);
            }
            graphics.drawImage(left, 0, i, null);
            graphics.drawImage(right, w + newX, i, null);

        }
        graphics.dispose();
        ImageIO.write(targetImage, fileName.getSuffix(), new File(targetFilePath));
    }

    /**
     * 手撕照片
     *
     * @param imageFilePath  源图片文件路径
     * @param targetFilePath 裁剪图片存放路径，需包含文件名
     * @param bgFilePath     背景图片文件路径
     * @param h              高度
     * @throws Exception 各种异常
     */
    public static void splitLongitudinalWithBg(String imageFilePath, String targetFilePath, String bgFilePath, int h) throws Exception {
        File imageFile = new File(imageFilePath);
        BufferedImage image = ImageIO.read(imageFile);
        boolean hasBg = StringUtil.isNotBlank(bgFilePath);
        BufferedImage bgImage = hasBg ? ImageIO.read(new File(bgFilePath)) : null;
        int width = image.getWidth();
        int height = image.getHeight();
        if (h > height / 2) {
            throw new RuntimeException("分割尺寸不能超过原图的二分之一");
        }
        // 输出图片
        FileName fileName = FileUtil.getFileName(imageFilePath);
        BufferedImage targetImage = new BufferedImage(width, height + h, BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics = targetImage.createGraphics();

        Random rand = new Random();
        int baseY = height / 2;
        int curY = baseY;
        boolean turnUp = true;
        for (int i = 0; i < width; i++) {
            // 使得不同裂痕的长度不一
            if (rand.nextBoolean()) {
                turnUp = !turnUp;
            }

            int length = rand.nextInt(10);
            if (turnUp) {
                if (baseY - curY - length > h) {
                    curY = curY + length;
                    turnUp = false;
                } else {
                    curY = curY - length;
                }
            } else {
                if (curY + length - baseY > h) {
                    curY = curY - length;
                    turnUp = true;
                } else {
                    curY = curY + length;
                }
            }

            BufferedImage up = image.getSubimage(i, 0, 1, curY);
            BufferedImage down = image.getSubimage(i, curY, 1, height - curY);
            if (hasBg) {
                BufferedImage bg = bgImage.getSubimage(i, 0, 1, h);
                graphics.drawImage(bg, i, curY, null);
            }
            graphics.drawImage(up, i, 0, null);
            graphics.drawImage(down, i, h + curY, null);
        }
        graphics.dispose();
        ImageIO.write(targetImage, fileName.getSuffix(), new File(targetFilePath));
    }

    /**
     * 手撕照片
     * 高度不增长
     *
     * @param imageFilePath  源图片文件路径
     * @param targetFilePath 裁剪图片存放路径，需包含文件名
     * @param bgFilePath     背景图片文件路径
     * @param baseY          撕裂位置
     * @param h              高度
     * @throws Exception 各种异常
     */
    public static void splitLongitudinalWithBg(String imageFilePath, String targetFilePath, String bgFilePath, int baseY, int h) throws Exception {
        File imageFile = new File(imageFilePath);
        BufferedImage image = ImageIO.read(imageFile);
        boolean hasBg = StringUtil.isNotBlank(bgFilePath);
        BufferedImage bgImage = hasBg ? ImageIO.read(new File(bgFilePath)) : null;
        int width = image.getWidth();
        int height = image.getHeight();
        if (h >= Math.min(baseY, width - baseY)) {
            throw new RuntimeException("分割尺寸不能超过分割位置");
        }
        // 输出图片
        FileName fileName = FileUtil.getFileName(imageFilePath);
        BufferedImage targetImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics = targetImage.createGraphics();

        Random rand = new Random();
        int curY = baseY;
        boolean turnUp = true;
        for (int i = 0; i < width; i++) {
            // 使得不同裂痕的长度不一
            if (rand.nextBoolean()) {
                turnUp = !turnUp;
            }

            int length = rand.nextInt(10);
            if (turnUp) {
                if (baseY - curY - length > h) {
                    curY = curY + length;
                    turnUp = false;
                } else {
                    curY = curY - length;
                }
            } else {
                if (curY + length - baseY > h) {
                    curY = curY - length;
                    turnUp = true;
                } else {
                    curY = curY + length;
                }
            }

            int newY = curY - h / 2;
            BufferedImage up = image.getSubimage(i, 0, 1, newY);
            BufferedImage down = image.getSubimage(i, newY + h, 1, height - newY - h);
            if (hasBg) {
                BufferedImage bg = bgImage.getSubimage(i, 0, 1, h);
                graphics.drawImage(bg, i, newY, null);
            }
            graphics.drawImage(up, i, 0, null);
            graphics.drawImage(down, i, h + newY, null);
        }
        graphics.dispose();
        ImageIO.write(targetImage, fileName.getSuffix(), new File(targetFilePath));
    }

    /**
     * 缩放图片
     *
     * @param imageFilePath  源图片文件路径
     * @param targetFilePath 裁剪图片存放路径，需包含文件名
     * @param w              宽度
     * @param h              高度
     * @throws Exception 各种异常
     */
    public static void scale(String imageFilePath, String targetFilePath, int w, int h) throws Exception {
        File imageFile = new File(imageFilePath);
        BufferedImage image = ImageIO.read(imageFile);
        Image resultImage = image.getScaledInstance(w, h, Image.SCALE_SMOOTH);
        // 将 Image转为BufferedImage
        BufferedImage tempImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics = tempImage.createGraphics();
        graphics.drawImage(resultImage, 0, 0, null);
        graphics.dispose();
        // 输出
        String suffix = FileUtil.getSuffix(imageFilePath);
        ImageIO.write(tempImage, suffix, new File(targetFilePath));
    }

    /**
     * 水平翻转图片 镜像
     *
     * @param imageFilePath  源图片文件路径
     * @param targetFilePath 裁剪图片存放路径，需包含文件名
     * @throws Exception 各种异常
     */
    public static void inversion(String imageFilePath, String targetFilePath) throws Exception {
        File imageFile = new File(imageFilePath);
        BufferedImage image = ImageIO.read(imageFile);
        int width = image.getWidth();
        int height = image.getHeight();
        // 竟然是一维数组 不是二维
        int[] rgb = image.getRGB(0, 0, width, height, null, 0, width);
        // 翻转
        int length = width / 2;
        for (int j = 0; j < height; j++) {
            int unit = j * width;
            for (int i = 0; i < length; i++) {
                int t = rgb[unit + i];
                rgb[unit + i] = rgb[unit + width - 1 - i];
                rgb[unit + width - 1 - i] = t;
            }
        }
        image.setRGB(0, 0, width, height, rgb, 0, width);
        // 输出
        String suffix = FileUtil.getSuffix(imageFilePath);
        ImageIO.write(image, suffix, new File(targetFilePath));
    }

    /**
     * 旋转图片
     *
     * @param imageFilePath  源图片文件路径
     * @param targetFilePath 裁剪图片存放路径，需包含文件名
     * @param angle          顺时针旋转角度
     * @throws Exception 各种异常
     */
    public static void rotate(String imageFilePath, String targetFilePath, int angle) throws Exception {
        if (angle < 0) {
            throw new RuntimeException("参数错误");
        }
        File imageFile = new File(imageFilePath);
        BufferedImage image = ImageIO.read(imageFile);
        int width = image.getWidth();
        int height = image.getHeight();
        // 计算旋转后的画布
        Rectangle rect = calcRotatedSize(width, height, angle);
        BufferedImage resultImage = new BufferedImage(rect.width, rect.height, BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics = resultImage.createGraphics();
        // 偏移
        graphics.translate((rect.width - width) / 2d, (rect.height - height) / 2d);
        // width/2 height/2 表示以这个坐标点为中心旋转
        graphics.rotate(Math.toRadians(angle), width / 2d, height / 2d);
        graphics.drawImage(image, 0, 0, null);
        graphics.dispose();
        // 输出
        String suffix = FileUtil.getSuffix(imageFilePath);
        ImageIO.write(resultImage, suffix, new File(targetFilePath));
    }

    private ImageUtil() {
    }

    /**
     * 截屏
     *
     * @return 截图文件输出路径
     */
    public static String captureScreen() {
        String filePath = OUT_PATH + File.separator + DateUtil.currentDateTimeNumeric() + DOT + FORMAT;
        return captureScreen(filePath);
    }

    /**
     * 截屏
     *
     * @param filePath 截图文件输出路径
     * @return 截图输出文件路径
     */
    public static String captureScreen(String filePath) {
        try {
            String format = FileUtil.getSuffix(filePath);
            ImageIO.write(captureScreenImage(), format, new File(filePath));
            return filePath;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 截屏
     *
     * @param outputStream 截图文件输出流
     */
    public static void captureScreen(OutputStream outputStream) {
        try {
            ImageIO.write(captureScreenImage(), FORMAT, outputStream);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * str2Image
     *
     * @param str        str
     * @param imagePath  输出图片文件路径
     * @param sideLength 图片正方形边长
     */
    public static void str2Image(String str, String imagePath, int sideLength) {
        int sideSquareCount = getSideSquareCount(str, sideLength);
        // 画图
        BufferedImage img = new BufferedImage(sideLength, sideLength, BufferedImage.TYPE_INT_RGB);
        Graphics g = img.getGraphics();
        // 画背景
        g.setColor(BG_COLOR);
        g.fillRect(0, 0, sideLength, sideLength);
        // 边长
        int baseSideLength = sideLength / sideSquareCount;
        int wordSideLength = 8 * baseSideLength;
        int sideWordCount = sideLength / wordSideLength;
        // 字点
        g.setColor(DOT_COLOR);
        char[] charArr = str.toCharArray();
        for (int i = 0; i < charArr.length; i++) {
            int x = (i % sideWordCount) * wordSideLength;
            int y = (i / sideWordCount) * wordSideLength;
            setSquareValue(g, x, y, baseSideLength, charArr[i]);
        }
        // 其余部分 渲染随机值
        Random random = new Random();
        int start = charArr.length;
        int end = sideWordCount * sideWordCount - 3;
        for (int i = start; i < end; i++) {
            int x = (i % sideWordCount) * wordSideLength;
            int y = (i / sideWordCount) * wordSideLength;
            setSquareValue(g, x, y, baseSideLength, random.nextInt(65536));
        }
        // 基础边长信息存储 使用65534是为了渲染最右下角 从左向右数第二个方块 此时计算最右下角方块的长度即可计算出基础边长
        setSquareValue(g, sideLength - wordSideLength, sideLength - wordSideLength, baseSideLength, 65534);
        // 字符串长度信息存储
        start = sideWordCount * sideWordCount - 3;
        int strLength = str.length();
        int length1 = strLength / 65536;
        int length2 = strLength % 65536;
        int x = (start % sideWordCount) * wordSideLength;
        int y = (start / sideWordCount) * wordSideLength;
        setSquareValue(g, x, y, baseSideLength, length1);
        x = ((start + 1) % sideWordCount) * wordSideLength;
        y = ((start + 1) / sideWordCount) * wordSideLength;
        setSquareValue(g, x, y, baseSideLength, length2);
        try {
            // PNG无损压缩，颜色不失真
            ImageIO.write(img, "PNG", new File(imagePath));
        } catch (Exception e) {
            PrintUtil.log("字符串转图片出错 str : {}, imagePath : {}", str, imagePath, e);
        }
    }

    /**
     * image2Str
     *
     * @param imagePath 图片文件路径
     * @return String
     */
    public static String image2Str(String imagePath) {
        BufferedImage image = null;
        try {
            image = ImageIO.read(new File(imagePath));
        } catch (Exception e) {
            PrintUtil.log("读取图片信息出错 imagePath : {}", imagePath, e);
            return null;
        }
        int width = image.getWidth();
        int height = image.getHeight();
        if (width != height) {
            PrintUtil.log("图片不是正方形");
            return null;
        }
        // 基础边长
        int end = width - 1;
        int bottomY = end;
        int start = -1;
        int bgColor = BG_COLOR.getRGB();
        while (start < end) {
            if (image.getRGB(end, bottomY) != bgColor) {
                break;
            }
            end--;
        }
        int baseSideLength = width - end - 1;
        int wordSideLength = 8 * baseSideLength;
        int sideWordCount = width / wordSideLength;
        // 字符串长度
        start = sideWordCount * sideWordCount - 3;
        int tempX = (start % sideWordCount) * wordSideLength;
        int tempY = (start / sideWordCount) * wordSideLength;
        int length1 = getSquareValue(image, tempX, tempY, baseSideLength);
        tempX = ((start + 1) % sideWordCount) * wordSideLength;
        tempY = ((start + 1) / sideWordCount) * wordSideLength;
        int length2 = getSquareValue(image, tempX, tempY, baseSideLength);
        int strLength = length1 * 65536 + length2;
        // 字符串
        StringBuilder sb = new StringBuilder(strLength);
        for (int i = 0; i < strLength; i++) {
            int x = (i % sideWordCount) * wordSideLength;
            int y = (i / sideWordCount) * wordSideLength;
            int value = getSquareValue(image, x, y, baseSideLength);
            char c = (char) value;
            sb.append(c);
        }
        return sb.toString();
    }

    /**
     * testImage1
     *
     * @param imagePath  输出图片文件路径
     * @param sideLength 图片正方形边长
     */
    public static void testImage1(String imagePath, int sideLength) {
        // 边长必须为奇数 方便取中心点
        if (!NumUtil.isOdd(sideLength)) {
            sideLength++;
        }
        // 画图
        BufferedImage img = new BufferedImage(sideLength, sideLength, BufferedImage.TYPE_INT_RGB);
        Graphics g = img.getGraphics();
        g.fillRect(0, 0, sideLength, sideLength);
        int baseX = sideLength / 2;
        int baseY = baseX;
        int count = 1;
        // 中心点
        drawPointByCount(g, count, baseX, baseY);
        count++;
        for (int i = 1; i < baseX + 1; i++) {
            int size = i * 2;
            int startX = baseX + i;
            int startY = baseY - i;
            // 上
            for (int j = 0; j < size; j++, count++) {
                drawPointByCount(g, count, startX, startY);
                startX--;
            }
            // 左
            for (int j = 0; j < size; j++, count++) {
                drawPointByCount(g, count, startX, startY);
                startY++;
            }
            // 下
            for (int j = 0; j < size; j++, count++) {
                drawPointByCount(g, count, startX, startY);
                startX++;
            }
            // 右
            for (int j = 0; j < size; j++, count++) {
                drawPointByCount(g, count, startX, startY);
                startY--;
            }
        }
        try {
            // PNG无损压缩，颜色不失真
            ImageIO.write(img, "PNG", new File(imagePath));
        } catch (Exception e) {
            PrintUtil.log("绘图出错 imagePath : {}", imagePath, e);
        }
    }

    /**
     * testImage2
     *
     * @param imagePath  输出图片文件路径
     * @param sideLength 图片正方形边长
     */
    public static void testImage2(String imagePath, int sideLength) {
        // 画图
        BufferedImage img = new BufferedImage(sideLength, sideLength, BufferedImage.TYPE_INT_RGB);
        Graphics g = img.getGraphics();
        int count = 1;
        int startX = 0;
        int startY = 0;
        // 左上半侧
        boolean isOdd = false;
        for (int i = 0; i < sideLength; i++) {
            int size = i + 1;
            isOdd = NumUtil.isOdd(i);
            for (int j = 0; j < size; j++, count++) {
                drawPointByCount(g, count, startX, startY);
                if (isOdd) {
                    startX--;
                    startY++;
                } else {
                    startX++;
                    startY--;
                }
            }
            // 位移到正确位置
            if (isOdd) {
                startX++;
            } else {
                startY++;
            }
        }
        if (isOdd) {
            startX++;
            startY--;
        } else {
            startY++;
            startX--;
        }
        // 右下半侧
        boolean negative = isOdd;
        for (int i = sideLength - 1; i > 0; i--) {
            for (int j = 0; j < i; j++, count++) {
                drawPointByCount(g, count, startX, startY);
                if (negative) {
                    startX++;
                    startY--;
                } else {
                    startX--;
                    startY++;
                }
            }
            // 位移到正确位置
            if (negative) {
                startY += 2;
                startX--;
            } else {
                startX += 2;
                startY--;
            }
            negative = !negative;
        }
        // 右下半侧
        try {
            // PNG无损压缩，颜色不失真
            ImageIO.write(img, "PNG", new File(imagePath));
        } catch (Exception e) {
            PrintUtil.log("绘图出错 imagePath : {}", imagePath, e);
        }
    }

    /**
     * testImage3
     *
     * @param imagePath  输出图片文件路径
     * @param sideLength 图片正方形边长
     * @param n          概率 n分之一
     */
    public static void testImage3(String imagePath, int sideLength, int n) {
        // 画图
        BufferedImage img = new BufferedImage(sideLength, sideLength, BufferedImage.TYPE_INT_RGB);
        Graphics g = img.getGraphics();
        Color color = Color.WHITE;
        g.setColor(color);
        // 随机
        Random rand = new Random();
        for (int i = 0; i < sideLength; i++) {
            for (int j = 0; j < sideLength; j++) {
                if (0 == rand.nextInt(n)) {
                    g.fillRect(i, j, 1, 1);
                }
            }
        }
        try {
            // PNG无损压缩，颜色不失真
            ImageIO.write(img, "PNG", new File(imagePath));
        } catch (Exception e) {
            PrintUtil.log("绘图出错 imagePath : {}", imagePath, e);
        }
    }

    /**
     * testImage4
     *
     * @param imagePath  输出图片文件路径
     * @param sideLength 图片正方形边长
     */
    public static void testImage4(String imagePath, int sideLength) {
        // 画图
        BufferedImage img = new BufferedImage(sideLength, sideLength, BufferedImage.TYPE_INT_RGB);
        Graphics g = img.getGraphics();
        // 颜色随机
        Random rand = new Random();
        int limit = 256 * 256 * 256;
        for (int i = 0; i < sideLength; i++) {
            for (int j = 0; j < sideLength; j++) {
                int num = rand.nextInt(limit);
                int red = (num >> 16) & 255;
                int green = (num >> 8) & 255;
                int blue = num & 255;
                // System.out.printf("[%d,%d,%d]%n", red, green, blue);
                // Color color = Color.decode(String.valueOf(num));
                Color color = new Color(red, green, blue);
                g.setColor(color);
                g.fillRect(i, j, 1, 1);
            }
        }
        try {
            // PNG无损压缩，颜色不失真
            ImageIO.write(img, "PNG", new File(imagePath));
        } catch (Exception e) {
            PrintUtil.log("绘图出错 imagePath : {}", imagePath, e);
        }
    }

    /**
     * draw
     *
     * @param g     Graphics
     * @param count count
     * @param x     x
     * @param y     y
     */
    private static void drawPointByCount(Graphics g, int count, int x, int y) {
        // 256 * 256 * 256
        if (count > 16777215) {
            int a = count / 16777216;
            int b = count % 16777216;
            if (NumUtil.isOdd(a)) {
                count = b;
            } else {
                count = 16777216 - b;
            }
        }
        byte[] bytes = ByteUtil.intTo4Byte(count);
        int red = byte2Int(bytes[1]);
        int green = byte2Int(bytes[2]);
        int blue = byte2Int(bytes[3]);
        Color color = new Color(red, green, blue);
        g.setColor(color);
        g.fillRect(x, y, 1, 1);
    }

    /**
     * byte2Int
     *
     * @param b byte
     * @return int
     */
    private static int byte2Int(byte b) {
        return b < 0 ? b + 256 : b;
    }

    /**
     * getSideSquareCount
     *
     * @param str        str
     * @param sideLength 边长
     * @return 单边方块个数
     */
    private static int getSideSquareCount(String str, int sideLength) {
        int maxLength = STR_LENGTH_ARR[STR_LENGTH_ARR.length - 1] - 3;
        if (StringUtil.isEmpty(str) || str.length() > maxLength) {
            throw new RuntimeException(String.format("字符串长度只能在1-%d之间", maxLength));
        }
        // 长度+3处理 最后三个8X像素方块 前两个存字符串长度 最后一个存基础边长
        int length = str.length() + 3;
        int lengthIndex = -1;
        for (int i = 0; i < STR_LENGTH_ARR.length; i++) {
            if (length <= STR_LENGTH_ARR[i]) {
                lengthIndex = i;
                break;
            }
        }
        int sideSquareCount = SIDE_SQUARE_COUNT_ARR[lengthIndex];
        int maxSquareCount = SIDE_SQUARE_COUNT_ARR[SIDE_SQUARE_COUNT_ARR.length - 1];
        if (0 != sideLength % sideSquareCount) {
            throw new RuntimeException(String.format("边长必须是%d的整数倍且必须小于等于%d", sideSquareCount, maxSquareCount));
        }
        return sideSquareCount;
    }

    /**
     * setSquareValue
     *
     * @param g              Graphics
     * @param x              x
     * @param y              y
     * @param baseSideLength 基础边长
     * @param value          int
     */
    private static void setSquareValue(Graphics g, int x, int y, int baseSideLength, int value) {
        char[] hexCharArr = getHexCharArr(value);
        int offset = 4 * baseSideLength;
        for (int i = 0; i < 4; i++) {
            int c = HEX_MAP.getOrDefault(hexCharArr[i], 0);
            // 15不画
            if (c == 15) {
                continue;
            }
            // xx,yy 表示字方块中的 字符方块坐标
            int xx = i % 2;
            int yy = i / 2;
            int wx = x + (c % 4) * baseSideLength + xx * offset;
            int wy = y + (c / 4) * baseSideLength + yy * offset;
            g.fillRect(wx, wy, baseSideLength, baseSideLength);
        }
    }

    /**
     * getSquareValue
     *
     * @param image          BufferedImage
     * @param x              x
     * @param y              y
     * @param baseSideLength 基础边长
     * @return value
     */
    private static int getSquareValue(BufferedImage image, int x, int y, int baseSideLength) {
        int dotColor = DOT_COLOR.getRGB();
        char[] charArr = new char[4];
        int offset = 4 * baseSideLength;
        charArr[0] = getSquareChar(image, x, y, baseSideLength, dotColor);
        charArr[1] = getSquareChar(image, x + offset, y, baseSideLength, dotColor);
        charArr[2] = getSquareChar(image, x, y + offset, baseSideLength, dotColor);
        charArr[3] = getSquareChar(image, x + offset, y + offset, baseSideLength, dotColor);
        String str = new String(charArr);
        return Integer.parseInt(str, 16);
    }

    /**
     * getSquareChar
     *
     * @param image          BufferedImage
     * @param x              x
     * @param y              y
     * @param baseSideLength 基础边长
     * @param dotColor       dotColor
     * @return char
     */
    private static char getSquareChar(BufferedImage image, int x, int y, int baseSideLength, int dotColor) {
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                int xx = x + j * baseSideLength;
                int yy = y + i * baseSideLength;
                int rgb = image.getRGB(xx, yy);
                if (rgb == dotColor) {
                    return HEX_DIGITS[i * 4 + j];
                }
            }
        }
        // 15不画 没有就15
        return HEX_DIGITS[15];
    }

    /**
     * 截屏
     *
     * @return 截屏图像数据
     * @throws java.lang.Exception 截屏错误
     */
    private static BufferedImage captureScreenImage() throws Exception {
        /*Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
            Rectangle screen = new Rectangle(screenSize);*/
        GraphicsDevice[] screenDevices = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
        int screenNum = screenDevices.length;
        GraphicsDevice screenDevice = screenDevices[0];
        DisplayMode displayMode = screenDevice.getDisplayMode();
        // 双屏时 默认两个屏等高等宽 左二右一 且平行排列的【因为目前我的屏幕放置就是这样的】
        // 单屏或超过双屏时只截取单个屏的
        Rectangle screen = screenNum == 2
                ? new Rectangle(-displayMode.getWidth(), 0, displayMode.getWidth() * 2, displayMode.getHeight())
                : new Rectangle(0, 0, displayMode.getWidth(), displayMode.getHeight());
        return new Robot().createScreenCapture(screen);
    }

    /**
     * 计算旋转后的画布大小
     *
     * @param width  原图宽度
     * @param height 原图高度
     * @param angle  顺时针旋转角度
     * @return 画布大小
     */
    private static Rectangle calcRotatedSize(int width, int height, double angle) {
        if (angle >= 90) {
            // 180的整数倍
            if (0 == angle % 180) {
                return new Rectangle(0, 0, width, height);
                // 90的整数倍
            } else if (0 == angle % 90) {
                return new Rectangle(0, 0, height, width);
            }
            // 角度的转换
            if (angle > 360) {
                angle = angle % 360;
            }
            if (angle < 180) {
                angle = 180 - angle;
            } else if (angle > 270) {
                angle = 360 - angle;
            }
        }

        double radians = Math.toRadians(angle);
        double r = Math.sqrt(width * width + height * height) / 2;
        double len = 2 * Math.sin(radians / 2) * r;
        double angleAlpha = (Math.PI - radians) / 2;
        double angleDeltaWidth = Math.atan(height * 1.0D / width);
        double angleDeltaHeight = Math.atan(width * 1.0D / height);

        int lenDeltaWidth = (int) Math.ceil(len * Math.cos(Math.PI - angleAlpha - angleDeltaWidth));
        int lenDeltaHeight = (int) Math.ceil(len * Math.cos(Math.PI - angleAlpha - angleDeltaHeight));

        int desWidth = width + lenDeltaWidth * 2;
        int desHeight = height + lenDeltaHeight * 2;
        desWidth = desWidth > 0 ? desWidth : -desWidth;
        desHeight = desHeight > 0 ? desHeight : -desHeight;
        return new Rectangle(0, 0, desWidth, desHeight);
    }

    /**
     * getHexInt
     *
     * @param c c
     * @return int
     */
    private static int getHexInt(char c) {
        Integer i = HEX_MAP.get(c);
        return null == i ? 0 : i + 1;
    }

    /**
     * getHexCharArr
     *
     * @param i i
     * @return char[]
     */
    private static char[] getHexCharArr(int i) {
        StringBuilder utf8Str = new StringBuilder(Integer.toHexString(i));
        while (utf8Str.length() < 4) {
            utf8Str.insert(0, '0');
        }
        return utf8Str.toString().toCharArray();
    }
}
